package net.iharder.dnd;

/**
 * An extension of {@link javax.swing.JList} that supports drag and drop to
 * rearrange its contents and to move objects in and out of the list. The
 * objects in the list will be passed either as a String by calling the object's
 * <tt>toString()</tt> object, or if your drag and drop target accepts the
 * {@link TransferableObject#DATA_FLAVOR} data flavor then the actual object
 * will be passed.
 * 
 * <p>
 * I'm releasing this code into the Public Domain. Enjoy.
 * </p>
 * <p>
 * <em>Original author: Robert Harder, rharder@usa.net</em>
 * </p>
 * 
 * @author Robert Harder
 * @author rharder@usa.net
 * @version 1.1
 */

@SuppressWarnings({ "unchecked", "unused", "serial" })
public class DnDList extends javax.swing.JList implements java.awt.dnd.DropTargetListener,
		java.awt.dnd.DragSourceListener, java.awt.dnd.DragGestureListener {

	private java.awt.dnd.DropTarget dropTarget = null;

	private java.awt.dnd.DragSource dragSource = null;

	private int sourceIndex = -1;
	private int dropIndex = -1;
	private Object sourceObject;

	/**
	 * Constructs a default {@link DnDList} using a
	 * {@link javax.swing.DefaultListModel}.
	 * 
	 * @since 1.1
	 */
	public DnDList() {
		super(new javax.swing.DefaultListModel());
		initComponents();
	} // end constructor

	/**
	 * Constructs a {@link DnDList} using the passed list model that must be
	 * extended from {@link javax.swing.DefaultListModel}.
	 * 
	 * @param model
	 *            The model to use
	 * @since 1.1
	 */
	public DnDList(javax.swing.DefaultListModel model) {
		super(model);
		initComponents();
	} // end constructor

	/**
	 * Constructs a {@link DnDList} by filling in a
	 * {@link javax.swing.DefaultListModel} with the passed array of objects.
	 * 
	 * @param data
	 *            The data from which to construct a list
	 * @since 1.1
	 */
	public DnDList(Object[] data) {
		this();
		((javax.swing.DefaultListModel) getModel()).copyInto(data);
	} // end constructor

	/**
	 * Constructs a {@link DnDList} by filling in a
	 * {@link javax.swing.DefaultListModel} with the passed
	 * {@link java.util.Vector} of objects.
	 * 
	 * @param data
	 *            The data from which to construct a list
	 * @since 1.1
	 */
	public DnDList(java.util.Vector data) {
		this();
		((javax.swing.DefaultListModel) getModel()).copyInto(data.toArray());
	} // end constructor

	private void initComponents() {
		dropTarget = new java.awt.dnd.DropTarget(this, this);
		dragSource = new java.awt.dnd.DragSource();
		dragSource.createDefaultDragGestureRecognizer(this, java.awt.dnd.DnDConstants.ACTION_MOVE,
				this);
	} // end initComponents

	/* ******** D R A G G E S T U R E L I S T E N E R M E T H O D S ******** */

	public void dragGestureRecognized(java.awt.dnd.DragGestureEvent event) { // System.out.println(
		// "DragGestureListener.dragGestureRecognized"
		// );
		final Object selected = getSelectedValue();
		if (selected != null) {
			sourceIndex = getSelectedIndex();
			java.awt.datatransfer.Transferable transfer = new TransferableObject(
					new TransferableObject.Fetcher() {
						/**
						 * This will be called when the transfer data is
						 * requested at the very end. At this point we can
						 * remove the object from its original place in the
						 * list.
						 */
						public Object getObject() {
							((javax.swing.DefaultListModel) getModel()).remove(sourceIndex);
							return selected;
						} // end getObject
					}); // end fetcher

			// as the name suggests, starts the dragging
			dragSource.startDrag(event, java.awt.dnd.DragSource.DefaultLinkDrop, transfer, this);
		}
		else {
			// System.out.println( "nothing was selected");
		}
	} // end dragGestureRecognized

	/* ******** D R A G S O U R C E L I S T E N E R M E T H O D S ******** */

	public void dragDropEnd(java.awt.dnd.DragSourceDropEvent evt) { // System.out.println(
		// "DragSourceListener.dragDropEnd"
		// );
	} // end dragDropEnd

	public void dragEnter(java.awt.dnd.DragSourceDragEvent evt) { // System.out.println(
		// "DragSourceListener.dragEnter"
		// );
	} // end dragEnter

	public void dragExit(java.awt.dnd.DragSourceEvent evt) { // System.out.println(
		// "DragSourceListener.dragExit"
		// );
	} // end dragExit

	public void dragOver(java.awt.dnd.DragSourceDragEvent evt) { // System.out.println(
		// "DragSourceListener.dragOver"
		// );
	} // end dragOver

	public void dropActionChanged(java.awt.dnd.DragSourceDragEvent evt) { // System.out.println(
		// "DragSourceListener.dropActionChanged"
		// );
	} // end dropActionChanged

	/* ******** D R O P T A R G E T L I S T E N E R M E T H O D S ******** */

	public void dragEnter(java.awt.dnd.DropTargetDragEvent evt) { // System.out.println(
		// "DropTargetListener.dragEnter"
		// );
		evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_MOVE);
	} // end dragEnter

	public void dragExit(java.awt.dnd.DropTargetEvent evt) { // System.out.println(
		// "DropTargetListener.dragExit"
		// );
	} // end dragExit

	public void dragOver(java.awt.dnd.DropTargetDragEvent evt) { // System.out.println(
		// "DropTargetListener.dragOver"
		// );
	} // end dragOver

	public void dropActionChanged(java.awt.dnd.DropTargetDragEvent evt) { // System.out.println(
		// "DropTargetListener.dropActionChanged"
		// );
		evt.acceptDrag(java.awt.dnd.DnDConstants.ACTION_MOVE);
	} // end dropActionChanged

	public void drop(java.awt.dnd.DropTargetDropEvent evt) { // System.out.println(
		// "DropTargetListener.drop"
		// );
		java.awt.datatransfer.Transferable transferable = evt.getTransferable();

		// If it's our native TransferableObject, use that
		if (transferable.isDataFlavorSupported(TransferableObject.DATA_FLAVOR)) {
			evt.acceptDrop(java.awt.dnd.DnDConstants.ACTION_MOVE);
			Object obj = null;
			try {
				obj = transferable.getTransferData(TransferableObject.DATA_FLAVOR);
			} // end try
			catch (java.awt.datatransfer.UnsupportedFlavorException e) {
				e.printStackTrace();
			} // end catch
			catch (java.io.IOException e) {
				e.printStackTrace();
			} // end catch

			if (obj != null) {
				// See where in the list we dropped the element.
				int dropIndex = locationToIndex(evt.getLocation());
				javax.swing.DefaultListModel model = (javax.swing.DefaultListModel) getModel();

				if (dropIndex < 0)
					model.addElement(obj);

				// Else is it moving down the list?
				else if (sourceIndex >= 0 && dropIndex > sourceIndex)
					model.add(dropIndex - 1, obj);
				else
					model.add(dropIndex, obj);

			} // end if: we got the object

			// Else there was a problem getting the object
			else {
				evt.rejectDrop();
			} // end else: can't get the object
		} // end if: it's a native TransferableObject

		// Else we can't handle this
		else
			evt.rejectDrop();
	} // end drop

} // end class DnDList
